Skip to content

Method: QueryHintsHandler.Hint(String, Object)

1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.model;
19:
20: import cz.cvut.kbss.jopa.query.QueryHints;
21: import cz.cvut.kbss.ontodriver.Statement;
22: import org.slf4j.Logger;
23: import org.slf4j.LoggerFactory;
24:
25: import java.util.HashMap;
26: import java.util.Locale;
27: import java.util.Map;
28: import java.util.Set;
29:
30: /**
31: * Processes query hints.
32: * <p>
33: * To add a new hint (inspired by Eclipselink):
34: * <ul>
35: * <li>Define a hint in {@link QueryHints}</li>
36: * <li>Ad an inner class extending {@link Hint} corresponding to the new hint. The class should implement the
37: * {@link Hint#applyToQuery(Object, AbstractQuery, Statement)} method and provide an array of values for transformation.</li>
38: * <li>Register the new hint class by calling {@link Hint#registerHint(Hint)} with an instance of the hint</li>
39: * </ul>
40: */
41: public class QueryHintsHandler {
42:
43: private static final Logger LOG = LoggerFactory.getLogger(QueryHintsHandler.class);
44:
45: private QueryHintsHandler() {
46: throw new AssertionError();
47: }
48:
49: /**
50: * Gets query hints supported by this implementation.
51: *
52: * @return A set of query hint names
53: */
54: public static Set<String> getSupportedHints() {
55: return Hint.getSupportedHints();
56: }
57:
58: /**
59: * Applies the specified hint value to the specified query.
60: * <p>
61: * Note that if the hint is unknown, it is ignored.
62: *
63: * @param hintName Query hint name
64: * @param hintValue Query hint value
65: * @param query Query to apply the hint value to
66: * @param statement OntoDriver statement that will be used to execute the specified query. The hint value may be
67: * applied directly to it
68: * @throws IllegalArgumentException If the hint value is not supported
69: */
70: public static void apply(String hintName, Object hintValue, AbstractQuery query, Statement statement) {
71: Hint.apply(hintName, hintValue, query, statement);
72: }
73:
74: /**
75: * Representation of a query hint.
76: * <p>
77: * The {@code valueArray} ensures that only supported values are provided to {@link #applyToQuery(Object,
78: * AbstractQuery, Statement)}. For example, if a hint is boolean-based and the configuration passes string "true",
79: * it is mapped to {@code true} based on {@code valueMap} which was constructed automatically from {@code
80: * valueArray}.
81: * <p>
82: * The {@code valueArray} is a two-dimensional array where each element is a single value, which in turn is a
83: * two-element array where the first element is a string representation of the value and the second one is the value
84: * itself.
85: */
86: abstract static class Hint {
87:
88: private static final Map<String, Hint> HINTS = new HashMap<>();
89:
90: Object[][] valueArray;
91: HashMap<String, Object> valueMap;
92: Object defaultValue;
93: String name;
94:
95: static {
96: registerHint(new DisableInferenceHint());
97: registerHint(new TargetOntologyHint());
98: }
99:
100: Hint(String name, Object defaultValue) {
101: this.name = name;
102: this.defaultValue = defaultValue;
103: }
104:
105: static void registerHint(Hint hint) {
106: hint.initialize();
107: HINTS.put(hint.name, hint);
108: }
109:
110: static Set<String> getSupportedHints() {
111: return HINTS.keySet();
112: }
113:
114: static String toUpperCase(String str) {
115: return str.toUpperCase(Locale.ROOT);
116: }
117:
118: static boolean shouldUseDefault(Object hintValue) {
119: return hintValue instanceof String && ((String) hintValue).isEmpty();
120: }
121:
122: static void apply(String hintName, Object hintValue, AbstractQuery query, Statement statement) {
123: if (!HINTS.containsKey(hintName)) {
124: LOG.warn("Unsupported query hint '{}'.", hintName);
125: return;
126: }
127: HINTS.get(hintName).apply(hintValue, query, statement);
128: }
129:
130: void apply(Object hintValue, AbstractQuery query, Statement statement) {
131: Object toApply = hintValue;
132: if (shouldUseDefault(hintValue)) {
133: toApply = defaultValue;
134: } else if (valueMap != null) {
135: toApply = valueMap.get(toUpperCase(hintValue.toString()));
136: if (toApply == null) {
137: throw new IllegalArgumentException("Unsupported value '" + hintValue + "' of hint '" + name + "'.");
138: }
139: }
140: applyToQuery(toApply, query, statement);
141: }
142:
143: void initialize() {
144: if (valueArray != null) {
145: this.valueMap = new HashMap<>(valueArray.length);
146: for (Object[] elem : valueArray) {
147: valueMap.put(toUpperCase(elem[0].toString()), elem[1]);
148: }
149: // Don't need it anymore
150: this.valueArray = null;
151: }
152: }
153:
154: /**
155: * Applies the specified value of this hint to the specified query (or statement, if more suitable).
156: *
157: * @param hintValue Value of the hint to apply
158: * @param query Query to apply the value to
159: * @param statement OntoDriver statement that will be used to execute the specified query. The hint value may be
160: * applied directly to it
161: */
162: abstract void applyToQuery(Object hintValue, AbstractQuery query, Statement statement);
163: }
164:
165: /**
166: * Allows disabling inference for query execution.
167: *
168: * @see QueryHints#DISABLE_INFERENCE
169: */
170: protected static class DisableInferenceHint extends Hint {
171: DisableInferenceHint() {
172: super(QueryHints.DISABLE_INFERENCE, Boolean.FALSE);
173: this.valueArray =
174: new Object[][]{{Boolean.TRUE.toString(), Boolean.TRUE}, {Boolean.FALSE.toString(), Boolean.FALSE}};
175: }
176:
177: @Override
178: void applyToQuery(Object hintValue, AbstractQuery query, Statement statement) {
179: assert statement != null;
180: if (Boolean.TRUE == hintValue) {
181: statement.disableInference();
182: if (query instanceof TypedQueryImpl) {
183: ((TypedQueryImpl<?>) query).getDescriptor().disableInference();
184: }
185: }
186: }
187: }
188:
189: protected static class TargetOntologyHint extends Hint {
190: public TargetOntologyHint() {
191: super(QueryHints.TARGET_ONTOLOGY, Statement.StatementOntology.SHARED);
192: this.valueArray = new Object[][]{{Statement.StatementOntology.SHARED.toString(),
193: Statement.StatementOntology.SHARED},
194: {Statement.StatementOntology.TRANSACTIONAL.toString(),
195: Statement.StatementOntology.TRANSACTIONAL}};
196: }
197:
198: @Override
199: void applyToQuery(Object hintValue, AbstractQuery query, Statement statement) {
200: assert hintValue instanceof Statement.StatementOntology;
201: statement.useOntology((Statement.StatementOntology) hintValue);
202: }
203: }
204: }